package com.gitee.qdbp.general.common.biz.settings.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.model.consts.Keys;
import com.gitee.qdbp.base.system.model.IAccount;
import com.gitee.qdbp.general.common.api.settings.basic.IAreaDivisionBasic;
import com.gitee.qdbp.general.common.api.settings.model.AreaBean;
import com.gitee.qdbp.general.common.api.settings.model.AreaChangedBean;
import com.gitee.qdbp.general.common.api.settings.model.AreaChangedBean.ChangeType;
import com.gitee.qdbp.general.common.api.settings.model.AreaChangedDetails;
import com.gitee.qdbp.general.common.api.settings.model.AreaDivisionBean;
import com.gitee.qdbp.general.common.api.settings.model.AreaDivisionUpdate;
import com.gitee.qdbp.general.common.api.settings.model.AreaDivisionWhere;
import com.gitee.qdbp.general.common.api.settings.service.IAreaDivisionExecutor;
import com.gitee.qdbp.general.common.enums.AreaType;
import com.gitee.qdbp.general.common.error.SettingsErrorCode;
import com.gitee.qdbp.general.thirdpart.api.map.service.IMapService;
import com.gitee.qdbp.tools.cache.ICacheService;
import com.gitee.qdbp.tools.codec.CodeTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

/**
 * 行政区划业务处理类
 *
 * @author zhaohuihua
 * @version 170702
 */
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public class AreaDivisionExecutor implements IAreaDivisionExecutor {

    private static final Logger log = LoggerFactory.getLogger(AreaDivisionExecutor.class);

    @Autowired
    private IAreaDivisionBasic areaDivisionBasic;
    @Autowired
    private AreaDivisionQueryer areaDivisionQueryer;
    @Autowired
    private IMapService mapService;
    @Autowired
    private ICacheService cacheService;

    @Override
    public void create(String scene, AreaBean model, IAccount me) throws ServiceException {
        String msg = "Failed to create AreaDivision. ";

        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(model)) {
            log.error(msg + "params is null: model");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        AreaDivisionBean bean = model.to(AreaDivisionBean.class);
        bean.setSceneType(scene);
        // 检查参数格式
        AreaDivisionTools.staticCheckCreateModel(bean, msg);
        // 如果没有简称, 自动填充简称
        AreaDivisionTools.fillShortName(bean, false);
        // 如果没有序号, 自动填充序号
        AreaDivisionTools.fillSortIndex(bean, false);

        String areaCode = bean.getAreaCode();
        String parentCode = bean.getParentCode();

        // 根据上级类型决定新增类型
        if (Keys.ROOT_CODE.equals(parentCode)) {
            bean.setType(AreaType.PROVINCE);
        } else {
            AreaDivisionBean older = findByAreaCodeThrowOnNotExists(scene, parentCode);
            switch (older.getType()) {
            case COUNTRY:
                bean.setType(AreaType.PROVINCE);
                break;
            case PROVINCE:
                bean.setType(AreaType.CITY);
                break;
            case CITY:
                bean.setType(AreaType.DISTRICT);
                break;
            case DISTRICT:
                bean.setType(AreaType.TOWNSHIP);
                break;
            case TOWNSHIP:
                throw new ServiceException(SettingsErrorCode.AREA_DIVISION_NOT_SUPPORT);
            }
        }

        // 检查区域编号
        AreaDivisionTools.staticCheckAreaCode(bean, msg);

        // 向gn_area_division表插入记录
        areaDivisionBasic.create(bean);
        // 更新缓存
        cacheService.hset(AreaDivisionQueryer.AREA_DIVISION_KEY, scene, areaCode, bean.to(AreaBean.class));
    }

    public void clearCache(String scene, IAccount me) throws ServiceException {
        // 清除缓存
        cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    /** 批量导入 **/
    public <T extends AreaBean> void imports(String scene, List<T> models, IAccount me) throws ServiceException {
        String msg = "Failed to import AreaDivision. ";

        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(models)) {
            log.error(msg + "params is null: models");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        List<AreaDivisionBean> list = new ArrayList<>();
        for (AreaBean model : models) {
            AreaDivisionBean bean = model.to(AreaDivisionBean.class);
            bean.setSceneType(scene);
            list.add(bean);

            // 检查参数格式
            AreaDivisionTools.staticCheckCreateModel(bean, msg);
            // 检查区域编号
            AreaDivisionTools.staticCheckAreaCode(bean, msg);
            // 如果没有简称, 自动填充简称
            AreaDivisionTools.fillShortName(bean, false);
            // 如果没有序号, 自动填充序号
            AreaDivisionTools.fillSortIndex(bean, false);

            // 检查数据重复
            // 目前实现的是全量覆盖, 因此可以在外部检查重复, 不需要查数据库
            // if (VerifyTools.isNotBlank(bean.getAreaCode())) {
            //     checkAreaCode(bean.getAreaCode());
            // }
            // checkAreaName(parentCode, null, bean.getAreaName());
            // checkShortName(parentCode, null, bean.getAreaName());
            // if (VerifyTools.isNotBlank(bean.getShortName())) {
            //     checkAreaName(parentCode, null, bean.getShortName());
            //     checkShortName(parentCode, null, bean.getShortName());
            // }
        }

        // 向gn_area_division表插入记录
        areaDivisionBasic.create(list);
        // 清除缓存(imports清除缓存在batch中执行)
        // cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    @Override
    public void update(AreaDivisionBean model, IAccount me) throws ServiceException {
        String msg = "Failed to update AreaDivision. ";

        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(model)) {
            log.error(msg + "params is null: model");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        String sceneType = model.getSceneType();
        if (VerifyTools.isBlank(sceneType)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        String id = model.getId();
        String areaCode = model.getAreaCode();
        if (VerifyTools.isAllBlank(id, areaCode)) {
            log.error(msg + "id or areaCode is required.");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        // 原始记录检查
        AreaDivisionBean older;
        if (VerifyTools.isBlank(id)) {
            older = areaDivisionBasic.findById(id);
            if (older == null) {
                log.error(msg + "record is not found. id=" + id);
                throw new ServiceException(ResultCode.RECORD_NOT_EXIST);
            }
        } else {
            AreaDivisionWhere w = new AreaDivisionWhere();
            w.setSceneType(sceneType);
            w.setAreaCode(areaCode);
            older = areaDivisionBasic.find(w);
            if (older == null) {
                log.error(msg + "record is not found. areaCode=" + areaCode);
                throw new ServiceException(ResultCode.RECORD_NOT_EXIST);
            }
        }

        AreaDivisionUpdate ud = new AreaDivisionUpdate();
        AreaBean newer = older.to(AreaBean.class);
        boolean changed = false;
        if (VerifyTools.isChanged(model.getAreaCode(), older.getAreaCode())) {
            changed = true;
            ud.setAreaCode(model.getAreaCode()); // 区域编号
            newer.setAreaCode(model.getAreaCode()); // 区域编号
        }
        if (VerifyTools.isChanged(model.getAreaName(), older.getAreaName())) {
            changed = true;
            ud.setAreaName(model.getAreaName()); // 区域名称
            newer.setAreaName(model.getAreaName()); // 区域名称
        }
        if (VerifyTools.isChanged(model.getShortName(), older.getShortName())) {
            changed = true;
            ud.setShortName(model.getShortName()); // 区域简称
            newer.setShortName(model.getShortName()); // 区域简称
        }
        if (VerifyTools.isChanged(model.getParentCode(), older.getParentCode())) {
            changed = true;
            ud.setParentCode(model.getParentCode()); // 上级行政区编号
            newer.setParentCode(model.getParentCode()); // 上级行政区编号
        }
        if (VerifyTools.isChanged(model.getSortIndex(), older.getSortIndex())) {
            changed = true;
            ud.setSortIndex(model.getSortIndex()); // 排序(越小越靠前)
            newer.setSortIndex(model.getSortIndex()); // 排序(越小越靠前)
        }
        if (VerifyTools.isChanged(model.getType(), older.getType())) {
            changed = true;
            ud.setType(model.getType()); // 类型(0.国|1.省|2.市|3.区/县)
            newer.setType(model.getType()); // 类型(0.国|1.省|2.市|3.区/县)
        }

        if (!changed) return;

        ud.setUpdateTimeToCurrent(true); // 更新时间
        AreaDivisionWhere where = ud.getWhere(true);
        where.setId(older.getId()); // 区域ID
        where.setSceneType(sceneType); // 使用场景

        // 更新gn_area_division表的记录
        areaDivisionBasic.update(ud, false);
        // 更新缓存
        cacheService.hset(AreaDivisionQueryer.AREA_DIVISION_KEY, sceneType, areaCode, newer);
        if (VerifyTools.isBlank(areaCode) && VerifyTools.notEquals(areaCode, older.getAreaCode())) {
            // 区域编号变更
            cacheService.hdel(AreaDivisionQueryer.AREA_DIVISION_KEY, sceneType, older.getAreaCode());
        }
    }

    public void clear(String scene, IAccount me) throws ServiceException {
        String msg = "Failed to clear AreaDivision. ";

        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        // 删除gn_area_division表的记录
        areaDivisionBasic.delete(new AreaDivisionWhere(), false);
        // 清除缓存
        cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    @Override
    public void delete(String scene, String id, IAccount me) throws ServiceException {
        String msg = "Failed to delete AreaDivision. ";

        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(id)) {
            log.error(msg + "params is null: id");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        // 从gn_area_division表查询原记录
        AreaDivisionBean older = areaDivisionBasic.findById(id);
        if (older == null) {
            log.error(msg + "record is not found.\n\tid is " + id);
            throw new ServiceException(ResultCode.RECORD_NOT_EXIST);
        }

        // 删除gn_area_division表的记录
        areaDivisionBasic.deleteByIds(Arrays.asList(id), false);
        // 清除缓存
        cacheService.hdel(AreaDivisionQueryer.AREA_DIVISION_KEY, scene, older.getAreaCode());
    }

    @Override
    public void delete(String scene, List<String> ids, IAccount me) throws ServiceException {
        String msg = "Failed to delete AreaDivision. ";

        if (VerifyTools.isBlank(me)) {
            log.error(msg + "params is null: me");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(ids)) {
            log.error(msg + "params is null: ids");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }

        // 删除gn_area_division表的记录
        areaDivisionBasic.deleteByIds(ids, false);
        // 清除缓存
        cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    private CodeTools codeTools = new CodeTools(0, 2);

    // 有些行政区划不是完全标准的, 上级编号不一定是下级编号前几位补0
    // 如果完全以AreaName作为KEY, 则district有可能重名, 导致误判
    // 因此只能以ProvinceCode:AreaShortName作为映射关系的KEY
    private String getAreaNameKey(AreaBean area) {
        String provinceCode = codeTools.top(area.getAreaCode());
        String shortName = AreaDivisionTools.generateShortName(area.getAreaName(), area.getType(), false);
        return area.getType().name() + ":" + provinceCode + ":" + shortName;
    }

    /** 与互联网上的最新行政区划比对, 返回变动情况 **/
    @Override
    @Transactional(readOnly = true)
    public List<AreaChangedDetails> compare(String scene, IAccount me) {
        String msg = "Failed to compare AreaDivision. ";
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        // 查询最新数据
        List<AreaBean> newList = mapService.queryAreaDivisions();
        // 查询数据库中的数据
        List<AreaBean> oldList = areaDivisionQueryer.listAll(scene);

        // 生成映射关系
        Map<String, AreaBean> newCodeMap = new HashMap<>();
        Map<String, AreaBean> newNameMap = new HashMap<>();
        for (AreaBean bean : newList) {
            newCodeMap.put(bean.getAreaCode(), bean);
            newNameMap.put(getAreaNameKey(bean), bean);
        }
        Map<String, AreaBean> oldCodeMap = new HashMap<>();
        Map<String, AreaBean> oldNameMap = new HashMap<>();
        for (AreaBean bean : oldList) {
            oldCodeMap.put(bean.getAreaCode(), bean);
            oldNameMap.put(getAreaNameKey(bean), bean);
        }

        // 新数据与旧数据比对, 找出变动数据
        List<AreaChangedDetails> list = new ArrayList<>();
        for (AreaBean newArea : newList) {
            AreaBean oldArea = oldCodeMap.get(newArea.getAreaCode());
            if (oldArea == null) { // 出现了新的编号
                oldArea = oldNameMap.get(getAreaNameKey(newArea));
                if (oldArea == null) { // 新增
                    AreaChangedDetails bean = new AreaChangedDetails();
                    bean.setType(ChangeType.CREATE);
                    bean.setNewer(newArea);
                    bean.setNewerAreaText(AreaDivisionTools.getAreaText(newArea.getAreaCode(), newCodeMap));
                    list.add(bean);
                } else { // 迁移, AreaDivision仍然存在, 但编号变化了
                    AreaChangedDetails bean = new AreaChangedDetails();
                    bean.setType(ChangeType.RENEW);
                    bean.setOlder(oldArea.to(AreaBean.class));
                    bean.setNewer(newArea);
                    bean.setOlderAreaText(AreaDivisionTools.getAreaText(oldArea.getAreaCode(), oldCodeMap));
                    bean.setNewerAreaText(AreaDivisionTools.getAreaText(newArea.getAreaCode(), newCodeMap));
                    list.add(bean);
                }
            } else { // 编号未变化
                if (!isEquals(newArea, oldArea)) { // AreaType或AreaName或ShortName有变化
                    AreaChangedDetails bean = new AreaChangedDetails();
                    bean.setType(ChangeType.UPDATE);
                    bean.setOlder(oldArea.to(AreaBean.class));
                    bean.setNewer(newArea);
                    bean.setOlderAreaText(AreaDivisionTools.getAreaText(oldArea.getAreaCode(), oldCodeMap));
                    bean.setNewerAreaText(AreaDivisionTools.getAreaText(newArea.getAreaCode(), newCodeMap));
                    list.add(bean);
                }
            }
        }

        // 旧数据与新数据比对, 找出已删除的数据
        for (AreaBean oldArea : oldList) {
            AreaBean newArea = newCodeMap.get(oldArea.getAreaCode());
            if (newArea == null) {
                newArea = newNameMap.get(getAreaNameKey(oldArea));
                if (newArea == null) { // 删除
                    AreaChangedDetails bean = new AreaChangedDetails();
                    bean.setType(ChangeType.DELETE);
                    bean.setOlder(oldArea.to(AreaBean.class));
                    bean.setOlderAreaText(AreaDivisionTools.getAreaText(oldArea.getAreaCode(), oldCodeMap));
                    list.add(bean);
                }
            }
        }

        // 排序
        Collections.sort(list);
        return list;
    }

    /** 保存变动情况 **/
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public <T extends AreaChangedBean> void saveChanged(String scene, List<T> changed, IAccount me)
            throws ServiceException {
        String msg = "Failed to save AreaDivisionChanged. ";
        if (VerifyTools.isBlank(scene)) {
            log.error(msg + "params is null: SceneType");
            throw new ServiceException(ResultCode.PARAMETER_IS_REQUIRED);
        }
        // 按类型归类
        List<AreaDivisionBean> createList = new ArrayList<>();
        List<AreaChangedBean> updateList = new ArrayList<>();
        List<String> deleteCodes = new ArrayList<>();
        for (AreaChangedBean bean : changed) {
            if (bean.getType() == ChangeType.CREATE) {
                AreaDivisionBean area = bean.getNewer().to(AreaDivisionBean.class);
                area.setSceneType(scene);
                AreaDivisionTools.fillSortIndex(area, false);
                createList.add(area);
            } else if (bean.getType() == ChangeType.DELETE) {
                deleteCodes.add(bean.getOlder().getAreaCode());
            } else if (bean.getType() == ChangeType.UPDATE) {
                updateList.add(bean);
            } else if (bean.getType() == ChangeType.RENEW) {
                deleteCodes.add(bean.getOlder().getAreaCode());
                AreaDivisionBean area = bean.getNewer().to(AreaDivisionBean.class);
                area.setSceneType(scene);
                AreaDivisionTools.fillSortIndex(area, false);
                createList.add(area);
            }
        }

        if (!createList.isEmpty()) { // 新增
            List<AreaDivisionBean> buffer = new ArrayList<>();
            for (AreaDivisionBean item : createList) {
                buffer.add(item);
                if (buffer.size() >= 20) {
                    areaDivisionBasic.create(buffer);
                    buffer.clear();
                }
            }
            if (!buffer.isEmpty()) {
                areaDivisionBasic.create(buffer);
                buffer.clear();
            }
        }
        if (!updateList.isEmpty()) { // 修改
            for (AreaChangedBean item : updateList) {
                AreaDivisionUpdate ud = new AreaDivisionUpdate();
                ud.setType(item.getNewer().getType());
                ud.setAreaName(item.getNewer().getAreaName());
                ud.setParentCode(item.getNewer().getParentCode());
                ud.setUpdateTimeToCurrent(true);
                if (VerifyTools.isBlank(item.getNewer().getShortName())) {
                    ud.setShortNameToNull(true);
                } else {
                    ud.setShortName(item.getNewer().getShortName());
                }
                AreaDivisionTools.fillShortName(ud, false);
                AreaDivisionWhere w = ud.getWhere(true);
                w.setSceneType(scene);
                w.setAreaCode(item.getNewer().getAreaCode());
                areaDivisionBasic.update(ud, false);
            }
        }
        if (!deleteCodes.isEmpty()) { // 删除
            AreaDivisionWhere where = new AreaDivisionWhere();
            where.setSceneType(scene);
            where.setAreaCodes(deleteCodes);
            areaDivisionBasic.delete(where, false);
        }
        // 清除缓存
        cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    /** 以互联网上的最新行政区划全量替换掉DB中的数据 **/
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void syncData(String scene, IAccount me) throws ServiceException {
        // 查询最新数据
        List<AreaBean> newList = mapService.queryAreaDivisions();
        // 清除gn_area_division表所有记录
        areaDivisionBasic.delete(new AreaDivisionWhere(), false);

        List<AreaDivisionBean> buffer = new ArrayList<>();
        for (AreaBean item : newList) {
            AreaDivisionBean bean = item.to(AreaDivisionBean.class);
            bean.setSceneType(scene);
            buffer.add(bean);
            if (buffer.size() >= 20) {
                areaDivisionBasic.create(buffer);
                buffer.clear();
            }
        }
        if (!buffer.isEmpty()) {
            areaDivisionBasic.create(buffer);
            buffer.clear();
        }
        // 清除缓存
        cacheService.del(AreaDivisionQueryer.AREA_DIVISION_KEY, scene);
    }

    private static boolean isEquals(AreaBean newer, AreaBean older) {
        return VerifyTools.equals(newer.getType(), older.getType())
                && VerifyTools.equals(newer.getParentCode(), older.getParentCode())
                && VerifyTools.equals(newer.getAreaName(), older.getAreaName())
                && VerifyTools.equals(newer.getShortName(), older.getShortName());
    }

    /** 根据编号查找地区, 如果用户不存在则报错 **/
    private AreaDivisionBean findByAreaCodeThrowOnNotExists(String scene, String areaCode) throws ServiceException {

        AreaDivisionWhere model = new AreaDivisionWhere();
        model.setAreaCode(areaCode);

        AreaDivisionBean bean = areaDivisionBasic.find(model);

        if (bean == null) {
            log.warn("AreaDivision not found, areaCode=" + areaCode);
            throw new ServiceException(SettingsErrorCode.AREA_DIVISION_NOT_EXIST);
        }
        return bean;
    }

}
